﻿namespace com.stifel
{
    using System;
    using System.Diagnostics;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading;

    using Microsoft.SharePoint.Administration;

    public static class ULSOBSOLETE
    {
        #region Constructors

        static ULSOBSOLETE( )
        {
            DefaultCategory = Category.General;
        }

        #endregion Constructors

        #region Enumerations

        /// <summary>
        /// Category of the message.
        /// </summary>
        public enum Category
        {
            ActiveDirectorySynchronization,
            General,
            FeatureInfrastructure,
            Fields,
            TimerJob,
            WebControls,
            WebParts,
            WorkflowInfrastructure
        }

        /// <summary>
        /// Logging level.
        /// </summary>
        public enum Level
        {
            Unassigned = 0,
            Critical = 1,
            Warning = 2,
            Information = 3,
            Exception = 4,
            Assert = 7,
            Unexpected = 10,
            Monitorable = 15,
            High = 20,
            Medium = 50,
            Verbose = 100,
        }

        #endregion Enumerations

        #region Properties

        public static Category DefaultCategory
        {
            get;
            set;
        }

        #endregion Properties

        #region Methods

        public static void LogCritical( string message, Category category )
        {
            var o = getAreaCategory( category );
            // queue message for logging
            queueMessageForULS( new MessageDefinition( Level.Critical, category, message ) );
        }

        public static void LogCritical( string message, Category category, params object[ ] args )
        {
            LogCritical( string.Format( message, args ), category );
        }

        public static void LogCritical( string message )
        {
            LogCritical( message, DefaultCategory );
        }

        public static void LogException( Exception exception )
        {
            LogException( exception, DefaultCategory );
        }

        public static void LogException( Exception exception, Category category )
        {
            string message = buildException( exception );

            // log
            LogCritical( message, category );
        }

        public static void LogMessage( string message, Category category, Level level, params object[ ] args )
        {
            LogMessage( message, null, category, level, args );
        }

        public static void LogMessage( string message, Exception exception, Category category, Level level, params object[ ] args )
        {
            var o = getAreaCategory( category );
            string m = buildMessage( message, exception, args );
            // queue message for logging
            queueMessageForULS( new MessageDefinition( level, category, m ) );
        }

        public static void LogMessage( string message, Exception exception, Level level, params object[ ] args )
        {
            LogMessage( message, exception, DefaultCategory, level, args );
        }

        public static void LogMessage( string message, Level level, params object[ ] args )
        {
            LogMessage( message, null, DefaultCategory, level, args );
        }

        public static void LogMessage( string message, Category category, Level level )
        {
            var o = getAreaCategory( category );
            string m = buildMessage( message, null, null );
            // queue message for logging
            queueMessageForULS( new MessageDefinition( level, category, message ) );
        }

        public static void LogMessage( string message, Level level )
        {
            LogMessage( message, DefaultCategory, level );
        }

        public static void LogWarning( string message, Category category )
        {
            var o = getAreaCategory( category );
            // queue message for logging
            queueMessageForULS( new MessageDefinition( Level.Warning, category, message ) );
        }

        public static void LogWarning( string message )
        {
            LogWarning( message, DefaultCategory );
        }

        public static void LogWarning( string message, Category category, params object[ ] args )
        {
            LogWarning( string.Format( message, args ), category );
        }

        static string buildException( Exception e )
        {
            string v = e.Message;
            string s = string.Empty;
            if ( !string.IsNullOrEmpty( v ) )
            {
                StringBuilder sb = new StringBuilder( );
                sb.AppendLine( "\bException ========================" );
                sb.AppendLine( v );
                sb.AppendLine( e.StackTrace );
                sb.AppendLine( "\b==================================" );
                s = sb.ToString( );
                sb = null;
            }
            return s;
        }

        static string buildMessage( string message, Exception exception, params object[ ] args )
        {
            Exception e = exception == null ? null : exception as Exception;

            string m = string.Empty;

            if ( args != null )
                m = string.Format( message, args );
            else
                m = message;

            StringBuilder sb = new StringBuilder( );
            {
                string s = m.Trim( );
                if ( !string.IsNullOrEmpty( s ) )
                    sb.AppendLine( s );
                else
                    sb.AppendLine( "\0" ); // placeholder
            }
            if ( e != null )
            {
                string s = buildException( e );
                if ( !string.IsNullOrEmpty( s ) )
                    sb.AppendLine( s );
            }
            {
                string[ ] a = sb.ToString( ).Split( new string[ ] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries );
                sb = new StringBuilder( );
                for ( int i = 0; i < a.Length; i++ )
                {
                    string tab = string.Empty;
                    tab = a[ i ].StartsWith( "\b" ) ? "\t" : "\t\t";
                    sb.AppendLine( string.Format( "{0}{1}", tab, a[ i ].Trim( ) ) );
                }
                string s = sb.ToString( );
                sb = null;
                return s.Replace( '\0', ' ' ).Replace( "\b", string.Empty ).TrimStart( '\t' ).TrimEnd( Environment.NewLine.ToCharArray( ) );
            }
        }

        static ULSAreaCategory getAreaCategory( Category category )
        {
            ULSAreaCategory o = new ULSAreaCategory( );
            switch ( category )
            {
                case Category.ActiveDirectorySynchronization:
                    o.Area = "Windows SharePoint";
                    o.Category = "AD Synchronization";
                    break;
                case Category.General:
                    o.Area = "Windows SharePoint";
                    o.Category = "General";
                    break;
                case Category.FeatureInfrastructure:
                    o.Area = "Windows SharePoint";
                    o.Category = "Feature Infrastructure";
                    break;
                case Category.Fields:
                    o.Area = "Windows SharePoint";
                    o.Category = "Fields";
                    break;
                case Category.TimerJob:
                    o.Area = "Timer Job";
                    o.Category = "Timer Jobs";
                    break;
                case Category.WebControls:
                    o.Area = "Windows SharePoint";
                    o.Category = "Web Controls";
                    break;
                case Category.WebParts:
                    o.Area = "Windows SharePoint";
                    o.Category = "Web Parts";
                    break;
                case Category.WorkflowInfrastructure:
                    o.Area = "Windows SharePoint";
                    o.Category = "Workflow Infrastructure";
                    break;
                default:
                    o.Area = "Windows SharePoint";
                    o.Category = "Unknown";
                    break;
            }
            return o;
        }

        static void queueMessageForULS( MessageDefinition state )
        {
            //ThreadPool.QueueUserWorkItem( writeMessageToULS, state );
            writeMessageToULS( state );
        }

        static void writeMessageToULS( object state )
        {
            MessageDefinition m = state as MessageDefinition;
            if ( m == null )
                return;

            ULSAreaCategory c = getAreaCategory( m.Category );
            string n = Assembly.GetExecutingAssembly( ).GetName( ).Name;

            TraceProvider.RegisterTraceProvider( );
            // log message to ULS
            TraceProvider.WriteTrace( 0, ( byte ) m.Level, Guid.NewGuid( ), n, c.Area, c.Category, m.Message );
            TraceProvider.UnregisterTraceProvider( );
        }

        #endregion Methods

        #region Nested Types

        struct ULSAreaCategory
        {
            #region Fields

            internal string Area;
            internal string Category;

            #endregion Fields
        }

        class MessageDefinition
        {
            #region Constructors

            public MessageDefinition( Level level, Category category, string message )
            {
                Level = level;
                Category = category;
                Message = message;
            }

            #endregion Constructors

            #region Properties

            public Category Category
            {
                get;
                private set;
            }

            public Level Level
            {
                get;
                private set;
            }

            public string Message
            {
                get;
                private set;
            }

            #endregion Properties
        }

        #endregion Nested Types
    }

    /// <summary>
    /// Provides logging capabilities for the Unified Logging Service (ULS) in SharePoint
    /// </summary>
    internal static class TraceProvider
    {
        #region Fields

        static UInt64 hTraceLog;
        static UInt64 hTraceReg;

        #endregion Fields

        #region Methods

        internal static unsafe void RegisterTraceProvider( )
        {
            SPFarm farm = SPFarm.Local;
            Guid traceGuid = farm.TraceSessionGuid;
            uint result = NativeMethods.RegisterTraceGuids( ControlCallback, null, ref traceGuid, 0, IntPtr.Zero, null, null, out hTraceReg );
            Debug.Assert( result == NativeMethods.ERROR_SUCCESS );
        }

        internal static uint TagFromString( string wzTag )
        {
            Debug.Assert( wzTag.Length == 4 );
            return ( uint ) ( wzTag[ 0 ] << 24 | wzTag[ 1 ] << 16 | wzTag[ 2 ] << 8 | wzTag[ 3 ] );
        }

        internal static void UnregisterTraceProvider( )
        {
            uint result = NativeMethods.UnregisterTraceGuids( hTraceReg );
            Debug.Assert( result == NativeMethods.ERROR_SUCCESS );
        }

        internal static void WriteTrace( uint tag, byte level, Guid correlationGuid, string exeName, string productName, string categoryName, string message )
        {
            const ushort sizeOfWCHAR = 2;
            NativeMethods.ULSTrace ulsTrace = new NativeMethods.ULSTrace( );

            // Pretty standard code needed to make things work
            ulsTrace.Header.Size = ( ushort ) Marshal.SizeOf( typeof( NativeMethods.ULSTrace ) );
            ulsTrace.Header.Flags = NativeMethods.WNODE_FLAG_TRACED_GUID;
            ulsTrace.ULSHeader.dwVersion = NativeMethods.TRACE_VERSION_CURRENT;
            ulsTrace.ULSHeader.dwFlags = NativeMethods.TraceFlags.TRACE_FLAG_ID_AS_ASCII;
            ulsTrace.ULSHeader.Size = ( ushort ) Marshal.SizeOf( typeof( NativeMethods.ULSTraceHeader ) );

            // Variables communicated to SPTrace
            ulsTrace.ULSHeader.Id = tag;
            ulsTrace.Header.Class.Level = level;
            ulsTrace.ULSHeader.wzExeName = exeName;
            ulsTrace.ULSHeader.wzProduct = productName;
            ulsTrace.ULSHeader.wzCategory = categoryName;
            ulsTrace.ULSHeader.wzMessage = message;
            ulsTrace.ULSHeader.correlationID = correlationGuid;

            // Optionally, to improve performance by reducing the amount of data copied around,
            // the Size parameters can be reduced by the amount of unused buffer in the Message
            if ( message.Length < 800 )
            {
                ushort unusedBuffer = ( ushort ) ( ( 800 - ( message.Length + 1 ) ) * sizeOfWCHAR );
                ulsTrace.Header.Size -= unusedBuffer;
                ulsTrace.ULSHeader.Size -= unusedBuffer;
            }

            if ( hTraceLog == 0 )
                RegisterTraceProvider( );

            if ( hTraceLog != 0 )
                NativeMethods.TraceEvent( hTraceLog, ref ulsTrace );
        }

        static unsafe uint ControlCallback( NativeMethods.WMIDPREQUESTCODE RequestCode, IntPtr Context, uint* InOutBufferSize, IntPtr Buffer )
        {
            uint Status;
            switch ( RequestCode )
            {
                case NativeMethods.WMIDPREQUESTCODE.WMI_ENABLE_EVENTS:
                    hTraceLog = NativeMethods.GetTraceLoggerHandle( Buffer );
                    Status = NativeMethods.ERROR_SUCCESS;
                    break;
                case NativeMethods.WMIDPREQUESTCODE.WMI_DISABLE_EVENTS:
                    hTraceLog = 0;
                    Status = NativeMethods.ERROR_SUCCESS;
                    break;
                default:
                    Status = NativeMethods.ERROR_INVALID_PARAMETER;
                    break;
            }

            *InOutBufferSize = 0;
            return Status;
        }

        #endregion Methods

        #region Nested Types

        internal static class NativeMethods
        {
            #region Fields

            internal const int ERROR_INVALID_PARAMETER = 87;
            internal const int ERROR_SUCCESS = 0;
            internal const int TRACE_VERSION_CURRENT = 1;
            internal const int WNODE_FLAG_TRACED_GUID = 0x00020000;

            #endregion Fields

            #region Enumerations

            internal enum TraceFlags
            {
                TRACE_FLAG_START = 1,
                TRACE_FLAG_END = 2,
                TRACE_FLAG_MIDDLE = 3,
                TRACE_FLAG_ID_AS_ASCII = 4
            }

            // Copied from Win32 APIs
            internal enum WMIDPREQUESTCODE
            {
                WMI_GET_ALL_DATA = 0,
                WMI_GET_SINGLE_INSTANCE = 1,
                WMI_SET_SINGLE_INSTANCE = 2,
                WMI_SET_SINGLE_ITEM = 3,
                WMI_ENABLE_EVENTS = 4,
                WMI_DISABLE_EVENTS = 5,
                WMI_ENABLE_COLLECTION = 6,
                WMI_DISABLE_COLLECTION = 7,
                WMI_REGINFO = 8,
                WMI_EXECUTE_METHOD = 9
            }

            #endregion Enumerations

            #region Delegates

            // Copied from Win32 APIs
            internal unsafe delegate uint EtwProc( NativeMethods.WMIDPREQUESTCODE requestCode, IntPtr requestContext, uint* bufferSize, IntPtr buffer );

            #endregion Delegates

            #region Methods

            // Copied from Win32 APIs
            [DllImport( "advapi32.dll", CharSet = CharSet.Unicode )]
            internal static extern UInt64 GetTraceLoggerHandle( [In]IntPtr Buffer );

            // Copied from Win32 APIs
            [DllImport( "advapi32.dll", CharSet = CharSet.Unicode )]
            internal static unsafe extern uint RegisterTraceGuids( [In] EtwProc cbFunc, [In] void* context, [In] ref Guid controlGuid, [In] uint guidCount, IntPtr guidReg, [In] string mofImagePath, [In] string mofResourceName, out ulong regHandle );

            // Copied from Win32 APIs
            [DllImport( "advapi32.dll", SetLastError = true )]
            internal static extern uint TraceEvent( [In]UInt64 traceHandle, [In]ref ULSTrace evnt );

            // Copied from Win32 APIs
            [DllImport( "advapi32.dll", CharSet = CharSet.Unicode )]
            internal static extern uint UnregisterTraceGuids( [In]ulong regHandle );

            #endregion Methods

            #region Nested Types

            // Copied from Win32 APIs
            [StructLayout( LayoutKind.Sequential )]
            internal struct EVENT_TRACE_HEADER
            {
                internal ushort Size;
                internal ushort FieldTypeFlags;
                internal EVENT_TRACE_HEADER_CLASS Class;
                internal uint ThreadId;
                internal uint ProcessId;
                internal Int64 TimeStamp;
                internal Guid Guid;
                internal uint ClientContext;
                internal uint Flags;
            }

            // Copied from Win32 APIs
            [StructLayout( LayoutKind.Sequential )]
            internal struct EVENT_TRACE_HEADER_CLASS
            {
                internal byte Type;
                internal byte Level;
                internal ushort Version;
            }

            [StructLayout( LayoutKind.Sequential )]
            internal struct ULSTrace
            {
                internal EVENT_TRACE_HEADER Header;
                internal ULSTraceHeader ULSHeader;
            }

            [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Unicode )]
            internal struct ULSTraceHeader
            {
                internal ushort Size;
                internal uint dwVersion;
                internal uint Id;
                internal Guid correlationID;
                internal TraceFlags dwFlags;
                [MarshalAs( UnmanagedType.ByValTStr, SizeConst = 32 )]
                internal string wzExeName;
                [MarshalAs( UnmanagedType.ByValTStr, SizeConst = 32 )]
                internal string wzProduct;
                [MarshalAs( UnmanagedType.ByValTStr, SizeConst = 32 )]
                internal string wzCategory;
                [MarshalAs( UnmanagedType.ByValTStr, SizeConst = 800 )]
                internal string wzMessage;
            }

            #endregion Nested Types
        }

        #endregion Nested Types
    }
}